/**
 * 文件上传 hook
 * @author luguoxiang
 * @date 2024-06-19
 * 开源项目：https://gitee.com/lgx1992/lg-soar 求star！请给我star！请帮我点个star！
 */
import {inject, onDeactivated, onMounted, provide, ref} from "vue";
import request from "@/soar/utils/request";
import {parseUrlParams, splice} from "@/soar/utils/data";
import message from "@/soar/utils/message";
import {getFileAccessAuthUrl, getFileExtension, sizeFormat, sizeParse} from "@/soar/utils/file";
import {syncLockLoad} from "@/soar/utils/loader";

const uploadUrl = '/general/file/upload';
const configUrl = '/general/file/config';

interface LgFile {
	id: string;
	url: string;
	name: string;
	size: number;
	status?: 0 | 1 | 2 | 3; // 0 已上传服务器， 1 等待，2 上传中，3 错误
	percent?: number;
}

function voToUrl(x: LgFile) {
	return `${x.url}/${x.name}?$=${x.size}`;
}

function urlToVo(x: string) {
	const vs = x.split('?');
	const us = vs[0].split('/');
	const url = us.slice(0, us.length - 1).join('/');
	const size = parseUrlParams(vs[1] || '').$;
	return { url, name: us[us.length - 1], size: Number(size || 0), id: url };
}

/**
 * 使用文件上传
 * @param options 配置参数
 * @param onChange 监听值变化
 */
export function useUploader(options: UseUploaderOptions, onChange?: (value: any) => void) {
	const {
		valueType = 'default',
		manual = false,
	} = options;

	const promises: Promise<any>[] = [];
	const files = ref<LgFile[]>([]);

	let blacklist: string[] = [];
	let whitelist: string[] = [];
	syncLockLoad('FileConfig', () => request.get(configUrl)).then((res: any) => {
		blacklist = res.data?.blacklist ?? [];
		whitelist = res.data?.whitelist ?? [];
	})

	/**
	 * 验证文件
	 * @param file
	 */
	function verify(file: LgFile) {
		// 文件数量限制
		if (options.count && files.value.length >= options.count) {
			message.error(`最多允许上传${options.count}个文件`);
			return false;
		}

		// 文件大小限制
		if (options.size) {
			const size = sizeParse(options.size);
			if (file.size > size) {
				message.error(`文件太大了, 限制${sizeFormat(size)}，实际${sizeFormat(file.size)}`);
				return false;
			}
		}

		if (!file.name) {
			return true;
		}
		// 限制文件类型
		const extension = getFileExtension(file.name);
		// 黑名单验证
		if (blacklist.includes(extension)) {
			message.error(`禁止上传”.${blacklist.join('，.')}“的文件`);
			return false;
		}
		// 白名单验证
		const accept = options.extension || [];
		if (accept.length) {
			// 是否满足后缀匹配
			const extensions = accept.filter((x) => !x.includes('/')).map((x) => new RegExp(x.replace('*', '.*')));
			for (const reg of extensions) {
				if (reg.test(extension)) {
					return true;
				}
			}
		}
		// 是否满足白名单
		if (whitelist.includes(extension)) {
			return true;
		}
		// 是否设置了白名单，若设置了白名单则报错
		if (accept.length || whitelist.length) {
			message.error(`文件类型不支持上传`);
			return false;
		}
		return true;
	}

	/**
	 * 上传文件
	 */
	function upload() {
		for (const i in files.value) {
			const item = files.value[i];
			if (item.status === 1 || item.status === 3) {
				const promise = request.upload(uploadUrl, {
					filePath: item.url,
					name: 'files',
					onProgressUpdate(res: any) {
						files.value[i].percent = res.progress;
					}
				}).then((res: any) => {
					res = res.data[0];
					files.value[i].status = 0;
					files.value[i].id = res.id;
					files.value[i].url = res.url;
					files.value[i].name = res.name;
					files.value[i].size = res.size;
					change();
					return true
				}).catch((err) => {
					item.status = 3;
				}).finally(() => {
					splice(promises, promise);
				})
				item.status = 2;
				item.percent = 0;
				promises.push(promise);
			}
		}
		return Promise.all(promises);
	}

	/**
	 * 选择文件
	 */
	function choose() {
		const config: any = {
			extension: options.extension,
			success(res: any) {
				for (let i = 0; i < res.tempFiles.length; i++) {
					const file: any = res.tempFiles[i];
					if (verify(file)) {
						files.value.push({
							id: String(Math.random()),
							url: file.path,
							size: file.size,
							name: file.name,
							status: 1,
							percent: 0,
						});
					}
				}
				change();
				if (!manual) {
					upload()
				}
			}
		}
		if (options.count) {
			config.count = options.count - files.value.length;
		}
		if (options.type === 'all') {
			uni.chooseFile(config)
		} else if (options.type === 'video') {
			uni.chooseVideo({
				...config,
				compressed: options.sizeType === 'compressed',
				sourceType: options.sourceType,
				maxDuration: options.maxDuration,
				success(res: any) {
					res.tempFile.path = res.tempFilePath
					// @ts-ignore
					res.tempFiles = [res.tempFile];
					config.success(res);
				}
			})
		} else {
			uni.chooseImage({
				...config,
				sizeType: options.sizeType,
				sourceType: options.sourceType,
				crop: options.crop,
			})
		}
	}

	/**
	 * 删除文件
	 * @param file LgFile对象或者索引
	 */
	function remove(file: LgFile | number) {
		if (typeof file === 'number') {
			files.value.splice(file, 1);
		} else {
			files.value.splice(files.value.indexOf(file), 1);
		}
		change();
	}

	let backup: any = null;
	/**
	 * 更新值
	 * @param value
	 */
	function updateValue(value: any) {
		if (backup === value) {
			return;
		}
		backup = value;
		switch (valueType) {
			case 'info':
				files.value = value ? [{ url: value.u, name: value.n, size: value.s, id: value.u }] : [];
				break;
			case 'infoList':
				files.value = value ? value.map((x: any) => ({ url: x.u, name: x.n, size: x.s, id: x.u })) : [];
				break;
			case 'url':
				// @ts-ignore
				files.value = value ? [urlToVo(value)] : [];
				break;
			case 'urlList':
				files.value = value.map(urlToVo);
				break;
			default:
				files.value = value || [];
				break;
		}
	}

	/**
	 * files变化
	 */
	function change() {
		let value: any = files.value;
		switch (valueType) {
			case 'info':
				// eslint-disable-next-line no-case-declarations
				const x = value[0] ?? {};
				value = { n: x.name, u: x.url, s: x.size };
				break;
			case 'infoList':
				value = value.map((x: any) => ({ n: x.name, u: x.url, s: x.size }));
				break;
			case 'url':
				value = value[0] ? voToUrl(value[0]) : '';
				break;
			case 'urlList':
				value = value.map(voToUrl);
				break;
			default:
				value = value.map((x: LgFile) => ({ name: x.name, url: x.url, size: x.size, id: x.id }));
				break;
		}
		backup = value;
		onChange?.(value);
	}

	onMounted(() => {
		inject('uploader:register', (e: any) => null)(upload);
		inject('async-component:register', (e: any) => null)(upload);
	});
	onDeactivated(() => {
		inject('uploader:deregister', (e: any) => null)(upload);
		inject('async-component:deregister', (e: any) => null)(upload);
	});

	function preview(file: LgFile) {
		uni.previewImage({
			current: getFileAccessAuthUrl(file.url),
			urls: files.value.map(x => getFileAccessAuthUrl(x.url))
		})
	}

	return {
		upload,
		choose,
		remove,
		updateValue,
		preview,
		files
	}
}

/**
 * 需要附件上传的表单使用，提交时调用 upload 方法等待所有附件上传完成才能提交表单
 */
export function useWaitUpload() {
	const set: Set<any> = new Set()
	provide('uploader:register', (i: any) => {
		set.add(i)
	})
	provide('uploader:deregister', (i: any) => {
		set.delete(i)
	})

	/**
	 * 上传所有未上传的附件，并等待上传完成
	 */
	function upload() {
		const promises = [];
		for (const x of set) {
			promises.push(x())
		}
		return Promise.all(promises)
	}

	return { upload }
}

export interface UseUploaderOptions {

	type?: 'image' | 'video' | 'all';

	extension?: string[];

	count?: number;

	sizeType?: 'original' | 'compressed';

	sourceType?: ['album' | 'camera'];

	crop?: any;

	maxDuration?: number;

	/**
	 * 文件大小限制
	 */
	size?: number | string;

	/**
	 * 值类型
	 */
	valueType?: 'default' | 'infoList' | 'urlList' | 'url' | 'info';

	/**
	 * 是否手动上传
	 */
	manual?: boolean;

}
